/**
 * Created by IntelliJ IDEA.
 * User: Egor
 * Date: 27.11.11
 * Time: 9:31
 * To change this template use File | Settings | File Templates.
 */
package org.electronic.registration.service {
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.external.ExternalInterface;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.utils.Proxy;
import flash.utils.flash_proxy;

import mx.core.mx_internal;
import mx.messaging.Channel;
import mx.messaging.ChannelSet;
import mx.messaging.channels.AMFChannel;
import mx.messaging.channels.SecureAMFChannel;
import mx.messaging.messages.ErrorMessage;
import mx.resources.IResourceManager;
import mx.resources.ResourceManager;
import mx.rpc.AbstractOperation;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.RemoteObject;
import mx.utils.URLUtil;

use namespace mx_internal;

public dynamic class RemoteService extends Proxy {

    private static const intrn:IResourceManager = ResourceManager.getInstance();

    private var callbackProps:Array = ["callback", "async"];
    private var callQueue:Array = new Array();
    private var ro:RemoteObject;

    private var _customFaultHandler:Function;

    private var currentUrl:String;

    /**
     * Be careful this value is synchronized with java constant in SessionPing.java
     */

    public function RemoteService(destination:String = null, customFaultHandler:Function = null) {
        ro = new RemoteObject(destination);

        if (ExternalInterface.available) {
            currentUrl = ExternalInterface.call("window.location.href.toString");
            currentUrl = currentUrl.substr(0, currentUrl.lastIndexOf("/"));
        }

        var cs:ChannelSet = new ChannelSet();
        if (currentUrl && URLUtil.isHttpsURL(currentUrl)) {
            var secureChannel:Channel = new SecureAMFChannel("my-secure-amf", currentUrl + "/messagebroker/amfsecure");
            cs.addChannel(secureChannel);
        } else {
            var simpleChannel:Channel = new AMFChannel("my-amf", currentUrl + "/messagebroker/amf");
            cs.addChannel(simpleChannel);
        }
        ro.channelSet = cs;

        ro.addEventListener(ResultEvent.RESULT, resultHandler);
        ro.addEventListener(FaultEvent.FAULT, extendedFaultHandler);
        this._customFaultHandler = customFaultHandler;
    }

    public function set destination(destination:String):void {
        ro.destination = destination;
    }

    public function addFaultHandler(handler:Function):void {
        ro.addEventListener(FaultEvent.FAULT, handler, false, 0, true);
    }

    private function resultHandler(event:ResultEvent):void {
        var callRow:CallRow = callQueue.shift();

        if (callRow.resultHandler != null) {
            callRow.resultHandler(event.result);
        }
        if (callQueue.length != 0) {
            var nextRow:CallRow = callQueue[0];
            if (!nextRow.executed) {
                executeOperation(nextRow);
            }
        }
    }

    private function extendedFaultHandler(event:FaultEvent):void {
        faultHandler(event);
    }

    private function faultHandler(event:FaultEvent):void {
        var message:ErrorMessage = event.message as ErrorMessage;
        if (!message) {
            message = new ErrorMessage();
            message.faultCode = event.fault.faultCode;
            message.faultDetail = event.fault.faultDetail;
            message.faultString = event.fault.faultString;
            message.rootCause = event.fault.rootCause;
        }
    }

    override flash_proxy function callProperty(methodName:*, ... args):* {
        var res:*;
        var method:String = methodName.toString();
        callOperation(method, args);
        return res;
    }

    private function hasOneProperty(obj:Object, names:Array):Boolean {
        for each (var name:String in names) {
            if (obj.hasOwnProperty(name)) {
                return true;
            }
        }
        return false;
    }

    private function callOperation(method:String, args:Array):void {
        var handler:Function = null;
        var async:Boolean = false;
        var params:Array = args;
        if (params.length != 0) {
            var lastParam:Object = params[params.length - 1];
            if (lastParam is Function) {
                handler = lastParam as Function;
                params.pop();
            } else {
                if (hasOneProperty(lastParam, callbackProps)) {
                    params.pop();
                }
                if (lastParam.hasOwnProperty("callback")) {
                    handler = lastParam.callback as Function;
                }
                if (lastParam.hasOwnProperty("async")) {
                    async = lastParam.async;
                }
            }
        }

        if (async) {
            asyncCall(handler, method, params);
        } else {
            syncCall(handler, method, params);
        }
    }

    private function syncCall(handler:Function, method:String, params:Array):void {
        var callRow:CallRow = new CallRow();
        callRow.methodName = method;
        callRow.params = params;
        callRow.resultHandler = handler;

        if (callQueue.length == 0) {
            executeOperation(callRow);
            callRow.executed = true;
        }

        callQueue.push(callRow);
    }

    private function executeOperation(callRow:CallRow):void {
        const operation:AbstractOperation = ro.getOperation(callRow.methodName);
        operation.arguments = callRow.params;
        operation.asyncRequest.reconnectAttempts = -1;
        operation.asyncRequest.reconnectInterval = 1000;
        operation.send();
    }

    private function asyncCall(handler:Function, method:String, params:Array):void {
        var asyncResultHandler:Function = function(event:ResultEvent):void {
            if (handler != null) {
                handler(event.result);
            }
        }
        var asyncRo:RemoteObject = new RemoteObject(ro.destination);
        asyncRo.addEventListener(ResultEvent.RESULT, asyncResultHandler);
        var operation:AbstractOperation = asyncRo.getOperation(method);
        operation.arguments = params;
        operation.send();
    }

    private function createFunction(method:String):* {
        var result:* = function(...args):void {
            callOperation(method, args);
        }
        return result;
    }

    override flash_proxy function getProperty(name:*):* {
        return createFunction(name.toString());
    }

    override flash_proxy function setProperty(name:*, value:*):void {
    }

}

}

import mx.rpc.AbstractOperation;

class CallRow {

    public var resultHandler:Function = null;
    public var methodName:String;
    public var params:Array;
    public var operation:AbstractOperation;
    public var executed:Boolean;
    public var attempt:int = 0;


    public function CallRow() {
    }
}